﻿using System;
using System.Collections.Generic;
using System.Reflection;
using MySql.Simple.Mapper.Attributes;
using MySql.Simple.Mapper.Structures;

namespace MySql.Simple.Mapper.Types
{
    /// <summary>
    /// Mapper class responsible for mapping data types to and from .NET / MySQL
    /// </summary>
    public class TypeMapper
    {
        #region Private

        /// <summary>
        /// Gets the column attribute attached to the given <see cref="ICustomAttributeProvider"/>.
        /// </summary>
        /// <param name="propertyInfo">The property info.</param>
        /// <returns></returns>
        private ColumnAttribute GetColumnAttribute(ICustomAttributeProvider propertyInfo)
        {
            ColumnAttribute columnAttribute = null;

            var attributes = propertyInfo.GetCustomAttributes(typeof(ColumnAttribute), true);

            foreach (var attribute in attributes)
            {
                columnAttribute = (ColumnAttribute)attribute;

                break;
            }

            return columnAttribute;
        }

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="TypeMapper"/> class.
        /// </summary>
        public TypeMapper()
        {
            // Add known types to the chain
            TypeChain = new List<ITypeLink>
                            {
                                new BooleanLink(),
                                new ByteLink(),
                                new CharLink(),
                                new DateTimeLink(),
                                new DoubleLink(),
                                new DecimalLink(),
                                new Int16Link(),
                                new Int32Link(),
                                new Int64Link(),
                                new SByteLink(),
                                new SingleLink(),
                                new StringLink(),
                                new UInt16Link(),
                                new UInt32Link(),
                                new UInt64Link(),
                                new EnumLink(),
                                new ObjectLink()
                            };
        }

        /// <summary>
        /// Gets or sets the type visitors.
        /// </summary>
        /// <value>The type visitors.</value>
        public IList<ITypeLink> TypeChain { get; set; }

        /// <summary>
        /// Maps the specified column.
        /// </summary>
        /// <param name="propertyInfo">The property info.</param>
        /// <returns></returns>
        public Column Map(PropertyInfo propertyInfo)
        {
            Column column = null;

            var columnAttribute = GetColumnAttribute(propertyInfo);

            if (columnAttribute != null)
            {
                // Assign values from attribute
                column = new Column
                             {
                                 Name = columnAttribute.Name,
                                 AutoIncrement = columnAttribute.AutoIncrement,
                                 PrimaryKey = columnAttribute.PrimaryKey,
                                 Nullable = columnAttribute.Nullable,
                                 Length = columnAttribute.Length,
                                 Type = columnAttribute.Type
                             };

                // Use property name if column name not set.
                if (string.IsNullOrEmpty(column.Name))
                {
                    column.Name = propertyInfo.Name;

                    column.Name = Inflector.Underscore(column.Name);
                }

                var mapped = false;

                // Assign type specific values
                foreach (var typeLink in TypeChain)
                {
                    // Is this link responsible for this type?
                    if (!typeLink.IsResponsible(propertyInfo)) continue;

                    // map the type information if so
                    column = typeLink.Map(column, columnAttribute);

                    mapped = true;

                    break;
                }

                if (!mapped) throw new ApplicationException("Could not map data type: " + propertyInfo.PropertyType);
            }

            return column;
        }
    }
}
